package proyectoX.src.aplicacion;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;

import javax.swing.JFrame;
import javax.swing.JLabel;

import proyectoX.src.controles.ControlOmega;
import proyectoX.src.entidades.Enemiga;
import proyectoX.src.entidades.Principal;
import proyectoX.src.entidades.entidad;
import proyectoX.src.fabricas.AbstractFactoryNaves;
import proyectoX.src.municiones.MunicionEspejo;
import proyectoX.src.municiones.MunicionX;
import proyectoX.src.powerups.Power_up;

import java.util.Vector;

/**
 * Clase principal del juego donde se mantiene un listado de niveles (en este caso un único nivel), el motor
 * central que procesa los elementos del juego, la fábrica de íconos, la interfaz de los íconos, la cantidad
 * de naves enemigas destruidas y el puntaje total del juego (suma entre todos los niveles).
 *
 */
public class Juego extends JFrame {


	//atributos de instancia
	protected int cantBajas;
	protected int puntajeGlobal;
	protected boolean juegoCorriendo;
	protected Vector<Nivel> niveles;  
	protected Nivel nivelActual;
	protected ControlOmega controlOmega;
	protected AbstractFactoryNaves fabricaIconos;
	protected InterfazIconos interfazIconos;
	protected boolean jefeActivado;


	//atributos gráficos
	protected static final int WIDTH = 580;
	protected static final int HEIGHT = 500;


	/**
	 * Inicializa todos los atributos y la GUI.
	 * 
	 * @param fabrica Fábrica de la cual se sacarán todos los íconos correspondientes y la nave elegida por el jugador. 
	 */
	public Juego(AbstractFactoryNaves fabrica) {
		super("Proyecto X");
		cantBajas = 0;
		puntajeGlobal = 0;
		juegoCorriendo = true;
		jefeActivado = false;
		niveles = new Vector<Nivel>();
		this.fabricaIconos = fabrica;
		inicializarGUI();
	}


	/**
	 * Crea la GUI determinando el tamaño de la ventana y dibujando el fondo además del panel derecho.
	 * Crea el motor de procesamiento del juego.
	 */
	private void inicializarGUI() {
		Container contenedor = this.getContentPane();	

		//creo una ventana centrada
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		Double anchoPantalla = screenSize.getWidth();
		Double altoPantalla = screenSize.getHeight();
		int auxAncho = (anchoPantalla.intValue() - WIDTH)/2;
		int auxAlto = (altoPantalla.intValue() - WIDTH)/2;
		this.setBounds(auxAncho, auxAlto, WIDTH, HEIGHT);  
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


		//agrego componentes gráficas y modifico valores gráficos
		this.setVisible(true);
		this.setLayout(null);
		this.setResizable(false);

		//Creo panel derecho, luego inicializo
		crearInterfazIconos();

		//agrego un nivel
		Nivel primerNivel = new Nivel(this);
		niveles.add(primerNivel);
		contenedor.add(primerNivel);
		nivelActual = primerNivel;

		//inicializo valores del panel de la dercha
		interfazIconos.inicializarValores();



		//CONTROL OMEGA
		controlOmega = new ControlOmega(this);
		this.addKeyListener(controlOmega);		//también controla el teclado para obtener controles fluídos
		controlOmega.start();

		this.repaint();  
	}

	/**
	 * Crea el panel derecho y lo agrega al contenedor del frame.
	 */
	private void crearInterfazIconos() {
		Container contenedor = this.getContentPane();
		// creo la interfaz que muestra los íconos a la derecha de la pantalla
		interfazIconos = new InterfazIconos(this);

		contenedor.add(interfazIconos);
	}

	/**
	 * Incrementa los puntos totales en la cantidad suministrada por parámetro.
	 * 
	 * @param puntaje Cantidad de puntos a incrementar.
	 */
	public void actualizarPuntaje(int puntaje) {
		puntajeGlobal += puntaje;
	}


	/**
	 * Al matar una nave enemiga es invocada para aumentar el contador correspondiente en 1.
	 */
	public void actualizarBajas() {
		cantBajas++;
	}

	/**
	 * Devuelve la cantidad de naves enemigas matadas por el jugador.
	 * 
	 * @return La cantidad de naves enemigas eliminadoas.
	 */
	public int getBajas() {
		return cantBajas;
	}

	/**
	 * Devuelve el ancho del frame.
	 * 
	 * @return El ancho del frame.
	 */
	public int getWidth() {
		return WIDTH;
	}


	/**
	 * Devuelve el alto del frame.
	 * 
	 * @return El alto del frame.
	 */
	public int getHeight() {
		return HEIGHT;
	}

	/**
	 * Devuelve el ancho del panel derecho.
	 * 
	 * @return El andho del panel derecho.
	 */
	public int getWidthInterfazIconos() {
		return interfazIconos.getWidth();
	}


	/**
	 * Devuelve el alto del panel derecho.
	 * @return El alto del panel derecho.
	 */
	public int getHeightInterfazIconos() {
		return interfazIconos.getHeight();
	}

	/**
	 * Devuelve el nivel en el que se está jugando actualmente.
	 * 
	 * @return El nivel en el que se está jugando.
	 */
	public Nivel getNivelActual() {
		return nivelActual;
	}

	/**
	 * Cuando se pierde, limpia la pantalla y setea el atributo en false para que se detenga el procesamiento del juego.
	 */
	public void GameOver(){
		controlOmega.limpiarPantalla();
		interfazIconos.eliminarPuntosDeVida();
		juegoCorriendo = false;
		repaint();
	}
	
	/**
	 * Cuando se gana, limpia la pantalla y setea el atributo en false para que se detenga el procesamiento del juego.
	 */
	public void victoria(){
		controlOmega.limpiarPantalla();
		juegoCorriendo = false;
		repaint();
	}


	/**
	 * Pasa una munición al motor para que la agregue al juego.
	 * 
	 * @param mun Munición a agregar.
	 */
	public void agregarMunicion(MunicionX mun) {
		controlOmega.agregarMunicion(mun);
	}

	/**
	 * Pasa una munición al motor para que la elimine del juego.
	 * 
	 * @param mun Munición a eliminar.
	 */
	public void quitarMunicion(MunicionX mun) {
		controlOmega.eliminarMunicion(mun);
	}

	/**
	 * Pasa una nave enemiga al motor para que la agregue al juego.
	 * 
	 * @param e Nave a agregar.
	 */
	public void agregarEnemiga(Enemiga e) {
		nivelActual.agregarEnemigaAFondo(e);
		controlOmega.agregarEnemiga(e);
	}

	/**
	 * Pasa una nave enemiga al motor para que la elimine del juego.
	 * 
	 * @param e Nave a eliminar.
	 */
	public void quitarEnemiga(Enemiga e) {
		controlOmega.eliminarEnemiga(e);
	}

	/**
	 * Pasa una powerup al motor para que la agregue al juego.
	 * 
	 * @param p Powerup a agregar.
	 */
	public void agregarPowerup(Power_up p) {
		controlOmega.agregarPowerup(p);		
	}

	/**
	 * Pasa una powerup al motor para que la elimine del juego.
	 * 
	 * @param p Powerup a eliminar.
	 */
	public void quitarPowerup(Power_up p) {
		controlOmega.eliminarPowerup(p);
	}


	/**
	 * Devuelve la condición del juego.
	 * 
	 * @return Si el juego está corriendo o no.
	 */
	public boolean obtenerJuegoCorriendo() {
		return juegoCorriendo;
	}


	/**
	 * Pasa una munición al motor para que calcule colisiones con naves enemigas.
	 * Devuelve un valor booleano correspondiente al éxito de la colisión.
	 * 
	 * @param m Munición que colisionará.
	 * @return Verdadero si colisionó, falso en caso contrario.
	 */
	public boolean calculoConEnemigas(MunicionX m){
		return controlOmega.controlarColicionNavesEnemigas(m);
	}

	/**
	 * Pasa una munición al motor para que calcule colisiones con la nave del jugador.
	 * Devuelve un valor booleano correspondiente al éxito de la colisión.
	 * 
	 * @param m Munición que colisionará.
	 * @return Verdadero si colisionó, falso en caso contrario.
	 */
	public boolean calculoConPrincipal(MunicionX m){
		return controlOmega.controlarColicionNavePrincipal(m);
	}


	/**
	 * Pasa un powerup al motor para que calcule colisiones con la nave jugadora.
	 * Devuelve un valor booleano correspondiente al éxito de la colisión.
	 * 
	 * @param p Powerup que colisionará.
	 * @return Verdadero si colisionó, falso en caso contrario.
	 */
	public boolean calculoConPowerup(Power_up p) {
		return controlOmega.controlarColisionPowerup(p);
	}

	/**
	 * Pasa una MunicionEspejo al motor para que calcule colisiones con todo tipo de municiones.
	 * Devuelve un valor booleano correspondiente al éxito de la colisión.
	 * 
	 * @param m MunicionEspejo que colisionará.
	 * @return Verdadero si colisionó, falso en caso contrario.
	 */
	public boolean calculoConMuniciones(MunicionEspejo m) {
		return controlOmega.controlarColisionMunicion(m);
	}

	/**
	 * Peticiona al motor a que limpie la pantalla.
	 */
	public void eliminarTodo(){
		controlOmega.limpiarPantalla();
	}

	/**
	 * Invocado por BombaDevastadora, pide al motor que elimine todas las naves enemigas en el juego.
	 * 
	 * Se imposibilita el uso de BombaDevastadora al llegar al jefe final para que no pueda ser destruido
	 * usándola.
	 */
	public void eliminarTodasLasNavesEnemigas(){
		if (!jefeActivado) {
			controlOmega.eliminarTodasLasEnemigas();
		}
	}

	//la razón para la sobrecarga es que para cada tipo de objeto realizamos un cálculo distinto

	/**
	 * Determina si una entidad (típicamente naves enemigas) se encuentra fuera de pantalla o no.
	 * 
	 * @param n Entidad en cuestión.
	 * @return Verdadero si está fuera de pantalla, falso en caso contrario.
	 */
	public boolean estaFueraDePantalla(entidad n) {
		return (( n.getX() > getWidth()-getWidthInterfazIconos() ) || (n.getX() < 0))||(( ( n.getY() > getHeight()+n.getHeight()) || (n.getY() < -500)));
	}


	/**
	 * Determina si una munición se encuentra fuera de pantalla o no.
	 * 
	 * @param n Munición en cuestión.
	 * @return Verdadero si está fuera de pantalla, falso en caso contrario.
	 */
	public boolean estaFueraDePantalla(MunicionX n)	{
		return (( n.getX() > getWidth()-getWidthInterfazIconos() ) || (n.getX() < 0))||(( ( n.getY() > getHeight()+n.getHeight() ) || (n.getY() < -n.getHeight())));
	}


	/**
	 * Determina si un powerup se encuentra fuera de pantalla o no.
	 * 
	 * @param p Powerup en cuestión.
	 * @return Verdadero si está fuera de pantalla, falso en caso contrario.
	 */
	public boolean estaFueraDePantalla(Power_up p) {
		JLabel n = p.getIcono();
		return (( n.getX() > getWidth()-getWidthInterfazIconos() ) || (n.getX() < 0))||(( ( n.getY() > getHeight()+n.getHeight() ) ));
	}


	/**
	 * Pide a nivel que elimine gráficamente del juego a la componente pasada por parámetro.
	 * 
	 * @param c Componente a eliminar.
	 */
	public void eliminarGraficamenteDelNivel(Component c) {
		nivelActual.eliminarGraficamente(c);
	}

	/**
	 * Detiene el juego actual y lo reinicia.
	 */
	public void reinicio(){
		juegoCorriendo = false;
		this.dispose();
		Main.reiniciar();
	}

	/**
	 * Invocado al inicializar la posición de la nave jugadora, se limpian las teclas para que no se mueva
	 * sin que hayan teclas presionadas.
	 */
	public void limpiarTeclas() {
		//if para evitar que sea invocado cuando se inicializa el juego por primera vez
		if (controlOmega != null) {
			controlOmega.limpiarTeclas();
		}
	}

	/**
	 * Pide a la fábrica que le dé un nuevo objeto correspondiente a la nave del jugador. 
	 * @param nivel Nivel actual.
	 * @return Nave del jugador.
	 */
	public Principal crearPrincipal(Nivel nivel) {
		return fabricaIconos.crearNavePrincipal(0, 0, nivel);
	}

	/**
	 * Devuelve la fábrica de íconos por si otra clase la quiere utilizar.
	 * @return La fábrica de íconos.
	 */
	public AbstractFactoryNaves getFabricaIconos() {
		return fabricaIconos;
	}

	/**
	 * Devuelve el panel derecho de la interfaz gráfica.
	 * @return El panel derecho.
	 */
	public InterfazIconos getInterfazIconos() {
		return interfazIconos;
	}

	/**
	 * Setea el flag correspondiente a la presencia del Jefe en el juego a true.
	 */
	public void activarJefe() {
		jefeActivado = true;
	}


	/**
	 * Le pide al motor que encuentre la nave enemiga más cercana al punto pasado por parámetro.
	 * 
	 * @param point Origen de la petición.
	 * @return La nave enemiga más cercana al punto.
	 */
	public Enemiga obtenerEnemigaCerca(Point point) {

		return controlOmega.obtenerEnemiga(point);
	}

}
